package org.swing;


import org.jdesktop.swingx.action.ActionManager;
import org.jdesktop.swingx.action.BoundAction;
import org.jdesktop.swingx.plaf.UIManagerExt;

import javax.swing.*;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Locale;


/**
 * <p>An extension of JModalFrame which supports application-wide modality.  </p>
 * <p>Project: JModalWindow - window-specific modality. </p>
 * <p>Copyright: Copyright (c) 2001-2006. </p>
 * <p/>
 * <p>This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version. </p>
 * <p/>
 * <p>This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details. </p>
 * <p/>
 * <p>You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA </p>
 * <p/>
 * <hr>
 * <p/>
 * <p><b>JModalDialog</b> was created to implement the same functionality as the JDialog,
 * in other words blocking all active frames and windows.
 * But use the same approach as the JModalFrame and thus the visual effect is the same. </p>
 *
 * @author Jene Jasper
 * @version 2.0
 */
public abstract class JModalDialog extends JModalFrame {
    public static final String UIPREFIX = "XDialog.";

    protected JComponent content;


    /**
     * <p>Default constructor for modal dialog. </p>
     */
    public JModalDialog() {
        this(null, null, "");
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param title frame title.
     */
    public JModalDialog(String title) {
        this(null, null, title);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner related window which could be blocked by this one.
     */
    public JModalDialog(Window owner) {
        this(owner, null, "");
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner       related window which could be blocked by this one.
     * @param returnFocus component in owner which should regain focus after closing this frame.
     */
    public JModalDialog(Window owner, Component returnFocus) {
        this(owner, returnFocus, "");
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner related window which could be blocked by this one.
     * @param title frame title.
     */
    public JModalDialog(Window owner, String title) {
        this(owner, null, title);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param gc the <code>GraphicsConfiguration</code> that is used
     *           to construct the new window with; if gc is <code>null</code>,
     *           the system default <code>GraphicsConfiguration</code>
     *           is assumed, unless <code>owner</code> is also null, in which
     *           case the <code>GraphicsConfiguration</code> from the
     *           shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(GraphicsConfiguration gc) {
        this(null, null, "", gc);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param title frame title.
     * @param gc    the <code>GraphicsConfiguration</code> that is used
     *              to construct the new window with; if gc is <code>null</code>,
     *              the system default <code>GraphicsConfiguration</code>
     *              is assumed, unless <code>owner</code> is also null, in which
     *              case the <code>GraphicsConfiguration</code> from the
     *              shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(String title, GraphicsConfiguration gc) {
        this(null, null, title, gc);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner related window which could be blocked by this one.
     * @param gc    the <code>GraphicsConfiguration</code> that is used
     *              to construct the new window with; if gc is <code>null</code>,
     *              the system default <code>GraphicsConfiguration</code>
     *              is assumed, unless <code>owner</code> is also null, in which
     *              case the <code>GraphicsConfiguration</code> from the
     *              shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(Window owner, GraphicsConfiguration gc) {
        this(owner, null, "", gc);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner       related window which could be blocked by this one.
     * @param returnFocus component in owner which should regain focus after closing this frame.
     * @param gc          the <code>GraphicsConfiguration</code> that is used
     *                    to construct the new window with; if gc is <code>null</code>,
     *                    the system default <code>GraphicsConfiguration</code>
     *                    is assumed, unless <code>owner</code> is also null, in which
     *                    case the <code>GraphicsConfiguration</code> from the
     *                    shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(Window owner, Component returnFocus, GraphicsConfiguration gc) {
        this(owner, returnFocus, "", gc);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner related window which could be blocked by this one.
     * @param title frame title.
     * @param gc    the <code>GraphicsConfiguration</code> that is used
     *              to construct the new window with; if gc is <code>null</code>,
     *              the system default <code>GraphicsConfiguration</code>
     *              is assumed, unless <code>owner</code> is also null, in which
     *              case the <code>GraphicsConfiguration</code> from the
     *              shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(Window owner, String title, GraphicsConfiguration gc) {
        this(owner, null, title, gc);
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner       related window which could be blocked by this one.
     * @param returnFocus component in owner which should regain focus after closing this frame.
     * @param title       frame title.
     */
    public JModalDialog(Window owner, Component returnFocus, String title) {
        super(owner, returnFocus, title, true);

        markAllWindows();
    }


    /**
     * <p>Constructor for modal dialog. </p>
     *
     * @param owner       related window which could be blocked by this one.
     * @param returnFocus component in owner which should regain focus after closing this frame.
     * @param title       frame title.
     * @param gc          the <code>GraphicsConfiguration</code> that is used
     *                    to construct the new window with; if gc is <code>null</code>,
     *                    the system default <code>GraphicsConfiguration</code>
     *                    is assumed, unless <code>owner</code> is also null, in which
     *                    case the <code>GraphicsConfiguration</code> from the
     *                    shared owner frame will be used.
     * @throws HeadlessException        if
     *                                  <code>GraphicsEnvironment.isHeadless()</code> returns true.
     * @throws IllegalArgumentException if <code>gc</code> is not from
     *                                  a screen device.
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public JModalDialog(Window owner, Component returnFocus, String title, GraphicsConfiguration gc) {
        super(owner, returnFocus, title, true, gc);

        markAllWindows();
    }

    /**
     * Creates a non-modal dialog with the given component as content and the
     * specified <code>Dialog</code> as owner.
     * <p>
     * @param dialog the owner
     * @param content the component to show and to auto-configure from.
     */
    public JModalDialog(Dialog dialog, JComponent content) {
        this(dialog);
        setContent(content);
    }

    /**
     * PENDING: widen access - this could be public to make the content really
     * pluggable?
     *
     * @param content
     */
    private void setContent(JComponent content) {
        if (this.content != null) {
            throw new IllegalStateException("content must not be set more than once");
        }
        initActions();
        Action contentCloseAction = content.getActionMap().get(ActionManager.CLOSE_ACTION_COMMAND);
        if (contentCloseAction != null) {
            putAction(ActionManager.CLOSE_ACTION_COMMAND, contentCloseAction);
        }
        Action contentExecuteAction = content.getActionMap().get(ActionManager.EXECUTE_ACTION_COMMAND);
        if (contentExecuteAction != null) {
            putAction(ActionManager.EXECUTE_ACTION_COMMAND, contentExecuteAction);
        }
        this.content = content;
        build();
        setTitleFromContent();
    }

    /**
     * Infers and sets this dialog's title from the the content.
     * Does nothing if content is null.
     *
     * Here: uses the content's name as title.
     */
    protected void setTitleFromContent() {
        if (content == null) return;
        setTitle(content.getName());
    }

    /**
     * pre: content != null.
     *
     */
    private void build() {
        JComponent contentBox = new Box(BoxLayout.PAGE_AXIS);
        contentBox.add(content);
        JComponent buttonPanel = createButtonPanel();
        contentBox.add(buttonPanel);
        contentBox.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
//        content.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);

//        fieldPanel.setAlignmentX();
//      buttonPanel.setAlignmentX(Component.RIGHT_ALIGNMENT);
        add(contentBox);

    }

    

    private void initActions() {
        Action defaultAction = createCloseAction();
        putAction(ActionManager.CLOSE_ACTION_COMMAND, defaultAction);
        putAction(ActionManager.EXECUTE_ACTION_COMMAND, defaultAction);
    }

    private Action createCloseAction() {
        String actionName = getUIString(ActionManager.CLOSE_ACTION_COMMAND);
        BoundAction action = new BoundAction(actionName,
                ActionManager.CLOSE_ACTION_COMMAND);
        action.registerCallback(this, "doClose");
        return action;
    }

    /**
     * Returns a potentially localized value from the UIManager. The given key
     * is prefixed by this component|s <code>UIPREFIX</code> before doing the
     * lookup. The lookup respects this table's current <code>locale</code>
     * property. Returns the key, if no value is found.
     *
     * @param key the bare key to look up in the UIManager.
     * @return the value mapped to UIPREFIX + key or key if no value is found.
     */
    protected String getUIString(String key) {
        return getUIString(key, getLocale());
    }

    /**
     * Returns a potentially localized value from the UIManager for the
     * given locale. The given key
     * is prefixed by this component's <code>UIPREFIX</code> before doing the
     * lookup. Returns the key, if no value is found.
     *
     * @param key the bare key to look up in the UIManager.
     * @param locale the locale use for lookup
     * @return the value mapped to UIPREFIX + key in the given locale,
     *    or key if no value is found.
     */
    protected String getUIString(String key, Locale locale) {
        String text = UIManagerExt.getString(UIPREFIX + key, locale);
        return text != null ? text : key;
    }




    /**
     * create the dialog button controls.
     *
     *
     * @return panel containing button controls
     */
    protected JComponent createButtonPanel() {
        // PENDING: this is a hack until we have a dedicated ButtonPanel!
        JPanel panel = new JPanel(new BasicOptionPaneUI.ButtonAreaLayout(true, 6))
        {
            public Dimension getMaximumSize() {
                return getPreferredSize();
            }
        };

        panel.setBorder(BorderFactory.createEmptyBorder(9, 0, 0, 0));
        Action executeAction = getAction(ActionManager.EXECUTE_ACTION_COMMAND);
        Action closeAction = getAction(ActionManager.CLOSE_ACTION_COMMAND);

        JButton findButton = new JButton(executeAction);
        panel.add(findButton);
        if (executeAction != closeAction) {
            panel.add(new JButton(closeAction));
        }


        KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);
        KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);

        InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.put(enterKey, ActionManager.EXECUTE_ACTION_COMMAND);
        inputMap.put(escapeKey, ActionManager.CLOSE_ACTION_COMMAND);

        getRootPane().setDefaultButton(findButton);
        return panel;
    }

    /**
     * convenience wrapper to access rootPane's actionMap.
     * @param key
     * @param action
     */
    private void putAction(Object key, Action action) {
        getRootPane().getActionMap().put(key, action);
    }

    /**
     * convenience wrapper to access rootPane's actionMap.
     *
     * @param key
     * @return root pane's <code>ActionMap</code>
     */
    private Action getAction(Object key) {
        return getRootPane().getActionMap().get(key);
    }



    /**
     * <p>Iterate all available frames. </p>
     */
    private void markAllWindows() {
        Frame[] frames = getFrames();

        for (int i = 0; i < frames.length; i++) {
            markWindowAndChildren(frames[i]);
        }
    }


    /**
     * <p>If the window isn't disposed yet then add it to the list of windows that should be blocked. </p>
     * <p/>
     * <p>Apply the same check recursively to the windows children. </p>
     *
     * @param window Window
     */
    private void markWindowAndChildren(Window window) {
        if (window.isDisplayable()) {
            Window[] children = window.getOwnedWindows();

            for (int i = 0; i < children.length; i++) {
                markWindowAndChildren(children[i]);
            }

            if (!window.equals(JModalWindow.getSharedOwnerFrame()) && !this.equals(window)) {
                addAdditionalModalToWindow(window);
            }
        }
    }
}
